
#include "ly/instances.hpp"
#include "ly/cell.hpp"
#include "ly/layout.hpp"
#include "ly/manager.hpp"
#include "ly/properties.hpp"

namespace ly {
/**
 *  @brief A base class for instance operations 
 *
 *  This class is used for the Op classes for the undo/redo queuing mechanism.
 */
class InstOpBase : public Op {
 public:
  InstOpBase() : Op() {}

  virtual void undo(Instances* instances) = 0;
  virtual void redo(Instances* instances) = 0;
};

// Template operation for component insertion/removal
template <typename Component>
class ComponentOp : public InstOpBase {

 public:
  typedef std::tuple<CplxTrans, Trans, ObjectWithProperties, RectangularArray, RegularArray> component_types;
  ComponentOp(bool is_insert, entt::entity e, const Component* comp_data = nullptr) : m_insert(is_insert), m_e(e) {
    if (comp_data) {
      m_data = *comp_data;
    }
  }

  void undo(Instances* insts) override {
    if (m_insert) {
      remove(insts);
    } else {
      insert(insts);
    }
  }

  void redo(Instances* insts) override {
    if (m_insert) {
      insert(insts);
    } else {
      remove(insts);
    }
  }

 private:
  void insert(Instances* insts) {
    auto& reg = insts->reg();
    if constexpr (std::is_same_v<Component, CellInst>) {
      reg.emplace_or_replace<CellInst>(m_e, m_data);
    } else if constexpr (std::is_same_v<Component, Trans>) {
      reg.emplace_or_replace<Trans>(m_e, m_data);
    } else if constexpr (std::is_same_v<Component, CplxTrans>) {
      reg.emplace_or_replace<CplxTrans>(m_e, m_data);
    } else if constexpr (std::is_same_v<Component, ObjectWithProperties>) {
      reg.emplace_or_replace<ObjectWithProperties>(m_e, m_data);
    } else if constexpr (std::is_same_v<Component, RectangularArray>) {
      reg.emplace_or_replace<RectangularArray>(m_e, m_data);
    } else if constexpr (std::is_same_v<Component, RegularArray>) {
      reg.emplace_or_replace<RegularArray>(m_e, m_data);
    } else if constexpr (std::is_same_v<Component, ExplicitArray>) {
      reg.emplace_or_replace<ExplicitArray>(m_e, m_data);
    } else if constexpr (std::is_same_v<Component, ExplicitXArray>) {
      reg.emplace_or_replace<ExplicitXArray>(m_e, m_data);
    } else if constexpr (std::is_same_v<Component, ExplicitYArray>) {
      reg.emplace_or_replace<ExplicitYArray>(m_e, m_data);
    }
  }

  void remove(Instances* insts) {
    auto& reg = insts->reg();
    reg.remove<Component>(m_e);
  }

  bool m_insert;
  entt::entity m_e;
  Component m_data;
};

Instances::Instances(/* args */) {}

Instances::~Instances() {}

bool Instances::is_editable() {
  return cell() && cell()->layout() ? cell()->layout()->is_editable() : true;
}

void Instances::emplace_or_replace(entt::entity e, const CellInst& com) {
  emplace(e, com);
};
void Instances::emplace_or_replace(entt::entity e, const Trans& com) {
  emplace(e, com);
};
void Instances::emplace_or_replace(entt::entity e, const CplxTrans& com) {
  emplace(e, com);
};
void Instances::emplace_or_replace(entt::entity e, const ObjectWithProperties& com) {
  emplace(e, com);
};
void Instances::emplace_or_replace(entt::entity e, const RegularArray& com) {
  emplace(e, com);
}
void Instances::emplace_or_replace(entt::entity e, const RectangularArray& com) {
  emplace(e, com);
}

void Instances::undo(Op* op) {
  InstOpBase* instop = dynamic_cast<InstOpBase*>(op);
  if (instop) {
    instop->undo(this);
  }
}

void Instances::redo(Op* op) {
  //  actions are only queued by the instance list - this is should be
  //  responsible for the handling of the latter.
  //  HACK: this is not really a nice concept, but it saves us a pointer to the manager.
  InstOpBase* instop = dynamic_cast<InstOpBase*>(op);
  if (instop) {
    instop->redo(this);
  }
}

void Instances::print_all() {
  // for (auto [e, inst] : m_reg.view<CellInst>().each()) {
  for (auto [e] : m_reg.view<entt::entity>().each()) {
    SPDLOG_INFO("entity: {}", size_t(e));
    std::string com_str = "";
    if (m_reg.try_get<CellInst>(e)) {
      com_str += "[CellInst] ";
    }
    if (m_reg.try_get<Trans>(e)) {
      com_str += "[Trans] ";
    }
    if (m_reg.try_get<CplxTrans>(e)) {
      com_str += "[CplxTrans] ";
      auto& ct = m_reg.get<CplxTrans>(e);
      ct.to_mat2();
    }
    if (m_reg.try_get<RegularArray>(e)) {
      com_str += "[RegularArray] ";
    }
    if (m_reg.try_get<ObjectWithProperties>(e)) {
      com_str += "[ObjectWithProperties] ";
    }
    SPDLOG_INFO("coms: {}", com_str);
  }
}

template <typename T>
void Instances::emplace(entt::entity e, const T& comp) {
  m_reg.emplace<T>(e, comp);
  if (cell()) {
    // bool editable = is_editable();
    if (cell()->manager() && cell()->manager()->transacting()) {
      // cell()->manager()->queue(cell().get(), new ComponentOp<T>(true, e, &comp));
    }
  }
};

}  // namespace ly
